Write a custom CUDA kernel to optimize `Log-Cosh Dice Loss`.

Formula: L = log(cosh(1 - Dice_Score))
Where Dice_Score = (2 * Sum(p * t)) / (Sum(p) + Sum(t) + eps).
p is softmax probability, t is one-hot target.

Problem Analysis:
1. Memory Bandwidth: The standard implementation (Softmax -> OneHot -> Element-wise Mul/Add -> Sum -> Div -> LogCosh) requires materializing full probability and target tensors (N, C, Spatial), leading to heavy global memory traffic.
2. Operator Fusion: The final Log-Cosh operation is a scalar mapping that can be easily fused into the reduction kernel.

Optimization Strategy: Fused Softmax-Reduction Kernel

1. One-Block-per-Sample: Assign one CUDA block to process one image (sample) in the batch.

2. Fused Softmax & Accumulation:
   - Iterate over spatial pixels using a grid-stride loop.
   - For each pixel, compute Softmax probabilities on-the-fly (requires Max and SumExp logic).
   - Accumulate `Intersection` (p * t) and `Union` (p + t) (or p^2 + t^2 depending on definition) in registers/shared memory.
   
3. Shared Memory Reduction:
   - Aggregate the intersection and union sums across all threads in the block.

4. Fused Post-Processing:
   - Thread 0 computes the Dice Score.
   - Thread 0 applies `log(cosh(1 - Dice))`.
   - Write the single scalar loss to global memory.
  
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn
import torch.nn.functional as F

BATCH_SIZE = 16
NUM_CLASSES = 4
DEPTH = 32
HEIGHT = 128
WIDTH = 128
SPATIAL_DIM = DEPTH * HEIGHT * WIDTH 
SHAPE_LOGITS = (BATCH_SIZE, NUM_CLASSES, DEPTH, HEIGHT, WIDTH)
SHAPE_TARGET = (BATCH_SIZE, DEPTH, HEIGHT, WIDTH)

EPS = 1e-6
REDUCTION = 'none'

class LogCoshDiceLoss(nn.Module):
    """
    Log-Cosh Dice Loss
    https://arxiv.org/pdf/2006.14822
    L = log(cosh(1 - Dice))
    """
    def __init__(self, eps=1e-6, reduction='mean'):
        super(LogCoshDiceLoss, self).__init__()
        self.eps = eps
        self.reduction = reduction

    def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
        # logits: (B, C, D, H, W)
        # targets: (B, D, H, W)
        
        probs = F.softmax(logits, dim=1)
        
        targets_onehot = F.one_hot(targets.long(), num_classes=logits.shape[1])
        # (B, D, H, W, C) -> (B, C, D, H, W)
        targets_onehot = targets_onehot.permute(0, 4, 1, 2, 3).float()
        
        # Compute Dice Score
        dims = (2, 3, 4)
        intersection = torch.sum(probs * targets_onehot, dim=dims)
        cardinality = torch.sum(probs + targets_onehot, dim=dims)
        
        dice_score = (2. * intersection) / (cardinality + self.eps)
        
        dice_score = dice_score.mean(dim=1)
        
        # Log-Cosh
        x = 1.0 - dice_score
        loss = torch.log(torch.cosh(x))
        
        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        return loss

class Model(nn.Module):
    def __init__(self, eps=1e-6, reduction='none'):
        super(Model, self).__init__()
        self.loss_fn = LogCoshDiceLoss(eps=eps, reduction=reduction)
    
    def forward(self, logits, targets):
        return self.loss_fn(logits, targets)

def get_inputs():
    logits = torch.randn(SHAPE_LOGITS, dtype=torch.float32)
    targets = torch.randint(0, NUM_CLASSES, SHAPE_TARGET, dtype=torch.long)
    return [logits.contiguous(), targets.contiguous()]

def get_init_inputs():
    return [EPS, REDUCTION]